#include <nxos.h>
#include <PowerBox.h>

NX_PRIVATE char powerBoxCmdBuf[PWBOX_CMD_BUF_LEN];
NX_PRIVATE char *powerBoxEnvLine = NX_NULL;
NX_PRIVATE NX_U16 lineCurpos;
NX_PRIVATE NX_U16 linePosition; 
NX_U16 currentHistory;
NX_U16 historyCount;
char cmdHistory[PWBOX_HISTORY_LINES][PWBOX_CMD_BUF_LEN];

NX_PRIVATE void PrintPrompt(char *line)
{
    char cwd[NX_FILE_MAX_PATH] = {0};
    PowerBoxGetCwd(cwd);
    if (line)
    {
        NX_Printf("PowerBox %s> %s", cwd, line);
    }
    else
    {
        NX_Printf("PowerBox %s> ", cwd);
    }
}

enum inputStat
{
    WAIT_NORMAL,
    WAIT_SPEC_KEY,
    WAIT_FUNC_KEY,
};

NX_IMPORT PowerBoxCmd powerBoxCmdTable[];
NX_IMPORT NX_Error PowerBoxBuiltinHelp(int argc, char *argv[], char * env);

static int StrMatch(const char *str1, const char *str2)
{
    const char *str = str1;

    while ((*str != 0) && (*str2 != 0) && (*str == *str2))
    {
        str ++;
        str2 ++;
    }

    return (str - str1);
}

void AutoCompletePath(char *path)
{
    NX_Solt dir = NX_SOLT_INVALID_VALUE;
    
    NX_Dirent dirent;
    char *full_path, *ptr, *index;

    if (!path)
        return;

    full_path = (char *)NX_MemAlloc(NX_FILE_MAX_PATH);
    if (full_path == NX_NULL) return; /* out of memory */

    if (*path != '/')
    {
        PowerBoxGetCwd(full_path);
        if (full_path[NX_StrLen(full_path) - 1]  != '/')
            NX_StrCat(full_path, "/");
    }
    else *full_path = '\0';

    index = NX_NULL;
    ptr = path;
    for (;;)
    {
        if (*ptr == '/') index = ptr + 1;
        if (!*ptr) break;

        ptr ++;
    }
    if (index == NX_NULL) index = path;

    if (index != NX_NULL)
    {
        char *dest = index;

        /* fill the parent path */
        ptr = full_path;
        while (*ptr) ptr ++;

        for (index = path; index != dest;)
            *ptr++ = *index++;
        *ptr = '\0';

        dir = NX_DirOpen(full_path);
        if (dir == NX_SOLT_INVALID_VALUE) /* open directory failed! */
        {
            NX_MemFree(full_path);
            return;
        }

        /* restore the index position */
        index = dest;
    }

    /* auto complete the file or directory name */
    if (*index == '\0') /* display all of files and directories */
    {
        for (;;)
        {
            if (NX_DirRead(dir, &dirent) != NX_EOK) break;

            NX_Printf("%s\n", dirent.name );
        }
    }
    else
    {
        int multi = 0;
        NX_Size length, min_length;

        min_length = 0;
        for (;;)
        {
            if (NX_DirRead(dir, &dirent) != NX_EOK) break;

            /* matched the prefix string */
            if (NX_StrCmpN(index, dirent.name, NX_StrLen(index)) == 0)
            {
                multi ++;
                if (min_length == 0)
                {
                    min_length = NX_StrLen(dirent.name);
                    /* save dirent name */
                    NX_StrCopy(full_path, dirent.name);
                }

                length = StrMatch(dirent.name, full_path);

                if (length < min_length)
                {
                    min_length = length;
                }
            }
        }

        if (min_length)
        {
            if (multi > 1)
            {
                /* list the candidate */
                NX_DirResetPointer(dir);

                for (;;)
                {
                    if (NX_DirRead(dir, &dirent) != NX_EOK) break;

                    if (NX_StrCmpN(index, dirent.name, NX_StrLen(index)) == 0)
                        NX_Printf("%s\n", dirent.name);
                }
            }

            length = index - path;
            NX_MemCopy(index, full_path, min_length);
            path[length + min_length] = '\0';

            /* try to locate folder */
            if (multi == 1)
            {
                NX_FileStatInfo buffer = {0};
                if ((NX_FileGetStatFromPath(path, &buffer) == 0))
                {
                    if(NX_FILE_IS_DIR(buffer.mode))
                    {
                        NX_StrCat(path, "/");
                    }
                }
            }
        }
    }

    NX_DirClose(dir);
    NX_MemFree(full_path);
}

void DoAutoComplete(char *prefix)
{
    int length, min_length;
    int matchs;
    const char *name_ptr, *cmname;
    PowerBoxCmd *index;

    min_length = 0;
    name_ptr = NX_NULL;

    if (*prefix == '\0')
    {
        PowerBoxBuiltinHelp(0, NX_NULL, NX_NULL);
        return;
    }

    /* check whether a spare in the command */
    {
        char *ptr;

        ptr = prefix + NX_StrLen(prefix);
        while (ptr != prefix)
        {
            if (*ptr == ' ')
            {
                AutoCompletePath(ptr + 1);
                break;
            }

            ptr --;
        }
        /* There is a chance that the user want to run the module directly. So
         * try to complete the file names. If the completed path is not a
         * module, the system won't crash anyway. */
        if (ptr == prefix)
        {
            AutoCompletePath(ptr);
        }
    }

    /* checks in internal command */
    {
        matchs = 0;
        for (index = &powerBoxCmdTable[0]; index->name != NX_NULL; index++)
        {
            /* skip finsh shell function */
            cmname = (const char *) index->name;
            if (NX_StrCmpN(prefix, cmname, NX_StrLen(prefix)) == 0)
            {
                matchs++;
                if (min_length == 0)
                {
                    /* set name_ptr */
                    name_ptr = cmname;
                    /* set initial length */
                    min_length = NX_StrLen(name_ptr);
                }

                length = StrMatch(name_ptr, cmname);

                if (length < min_length)
                    min_length = length;

                NX_Printf("%s\n", cmname);
            }
        }
    }

    /* auto complete string */
    if (name_ptr != NX_NULL)
    {
        /* find match cmd count  */
        if (matchs == 1)
        {
            NX_StrCopy(prefix, name_ptr);
        }
        else
        {
            NX_StrCopyN(prefix, name_ptr, min_length);
        }
    }

    return ;
}

static void autoComplete(char *prefix)
{
    NX_Printf("\n");
    DoAutoComplete(prefix);

    PrintPrompt(prefix);
}

#ifdef PWBOX_USE_HISTORY
static void handleHistory(void)
{
#if defined(_WIN32)
    int i;
    NX_Printf("\r");

    for (i = 0; i <= 60; i++)
        putchar(' ');
    NX_Printf("\r");

#else
    NX_Printf("\033[2K\r");
#endif
    PrintPrompt(powerBoxCmdBuf);
}

static void pushHistory(void)
{
    if (linePosition != 0)
    {
        /* push history */
        if (historyCount >= PWBOX_HISTORY_LINES)
        {
            /* if current cmd is same as last cmd, don't push */
            if (NX_MemCompareN(&cmdHistory[PWBOX_HISTORY_LINES - 1], powerBoxCmdBuf, PWBOX_CMD_BUF_LEN))
            {
                /* move history */
                int index;
                for (index = 0; index < PWBOX_HISTORY_LINES - 1; index ++)
                {
                    NX_MemCopy(&cmdHistory[index][0],
                           &cmdHistory[index + 1][0], PWBOX_CMD_BUF_LEN);
                }
                NX_MemSet(&cmdHistory[index][0], 0, PWBOX_CMD_BUF_LEN);
                NX_MemCopy(&cmdHistory[index][0], powerBoxCmdBuf, linePosition);

                /* it's the maximum history */
                historyCount = PWBOX_HISTORY_LINES;
            }
        }
        else
        {
            /* if current cmd is same as last cmd, don't push */
            if (historyCount == 0 || NX_MemCompareN(&cmdHistory[historyCount - 1], powerBoxCmdBuf, PWBOX_CMD_BUF_LEN))
            {
                currentHistory = historyCount;
                NX_MemSet(&cmdHistory[historyCount][0], 0, PWBOX_CMD_BUF_LEN);
                NX_MemCopy(&cmdHistory[historyCount][0], powerBoxCmdBuf, linePosition);

                /* increase count and set current history position */
                historyCount ++;
            }
        }
    }
    currentHistory = historyCount;
}
#endif

NX_PRIVATE NX_Error ReadCmd(int termFd, char *cmd, int cmdlen)
{
    char ch;
    enum inputStat stat;

    stat = WAIT_NORMAL;

    while (1)
    {
        ch = '\0';
        /* read input key */
        if (NX_FileRead(termFd, &ch, 1) != 1)
        {
            continue;
        }
        
        if (!ch)
        {
            continue;
        }

        /*
         * handle control key
         * up key  : 0x1b 0x5b 0x41
         * down key: 0x1b 0x5b 0x42
         * right key:0x1b 0x5b 0x43
         * left key: 0x1b 0x5b 0x44
         */
        if (ch == 0x1b)
        {
            stat = WAIT_SPEC_KEY;
            continue;
        }
        else if (stat == WAIT_SPEC_KEY)
        {
            if (ch == 0x5b)
            {
                stat = WAIT_FUNC_KEY;
                continue;
            }

            stat = WAIT_NORMAL;
        }
        else if (stat == WAIT_FUNC_KEY)
        {
            stat = WAIT_NORMAL;

            if (ch == 0x41) /* up key */
            {
                // NX_Printf("!up\n");
#ifdef PWBOX_USE_HISTORY
                /* prev history */
                if (currentHistory > 0)
                    currentHistory --;
                else
                {
                    currentHistory = 0;
                    continue;
                }

                /* copy the history command */
                NX_MemCopy(cmd, &cmdHistory[currentHistory][0],
                       PWBOX_CMD_BUF_LEN);
                lineCurpos = linePosition = (NX_U16)NX_StrLen(cmd);
                handleHistory();
#endif
                continue;
            }
            else if (ch == 0x42) /* down key */
            {

                // NX_Printf("!down\n");
#ifdef PWBOX_USE_HISTORY
                /* next history */
                if (currentHistory < historyCount - 1)
                    currentHistory ++;
                else
                {
                    /* set to the end of history */
                    if (historyCount != 0)
                        currentHistory = historyCount - 1;
                    else
                        continue;
                }

                NX_MemCopy(cmd, &cmdHistory[currentHistory][0],
                       PWBOX_CMD_BUF_LEN);
                lineCurpos = linePosition = (NX_U16)NX_StrLen(cmd);
                handleHistory();
#endif
                continue;
            }
            else if (ch == 0x44) /* left key */
            {
                // NX_Printf("!left\n");
                if (lineCurpos)
                {
                    NX_Printf("\b");
                    lineCurpos --;
                }
                continue;
            }
            else if (ch == 0x43) /* right key */
            {
                // NX_Printf("!right\n");
                if (lineCurpos < linePosition)
                {
                    NX_Printf("%c", cmd[lineCurpos]);
                    lineCurpos ++;
                }
                continue;
            }
        }
        if (ch == '\0' || ch == 0xFF)
        {
            continue;
        }
        
        /* parse data */
        switch (ch)
        {
        case '\t':
            
            /* move the cursor to the beginning of line */
            for (int i = 0; i < lineCurpos; i++)
                NX_Printf("\b");

            /* auto complete */
            autoComplete(&cmd[0]);
            /* re-calculate position */
            lineCurpos = linePosition = (NX_U16)NX_StrLen(cmd);
            break;
        case '\r':
        case '\n':
            NX_Printf("\n");
            if (cmd[0] != '\0')
            {
#ifdef PWBOX_USE_HISTORY
                pushHistory();
#endif
                return NX_EOK;
            }
            else
            {
                return NX_EAGAIN;
            }
            break;
        case '\b':
        case 0x7f:
            if (lineCurpos == 0)
            {
                break;
            }
            lineCurpos--;
            linePosition--;
            if (lineCurpos < linePosition)
            {
                int i;

                NX_MemMove(&cmd[lineCurpos],
                           &cmd[lineCurpos + 1],
                           linePosition - lineCurpos);
                cmd[linePosition] = 0;

                NX_Printf("\b%s  \b", &cmd[lineCurpos]);

                /* move the cursor to the origin position */
                for (i = lineCurpos; i <= linePosition; i++)
                    NX_Printf("\b");
            }
            else
            {
                NX_Printf("\b \b");
                cmd[linePosition] = 0;
            }
            break;
        default:
            {
                /* it's a large line, discard it */
                if (linePosition >= PWBOX_CMD_BUF_LEN)
                    linePosition = 0;

                /* normal character */
                if (lineCurpos < linePosition)
                {
                    int i;

                    NX_MemMove(&cmd[lineCurpos + 1],
                            &cmd[lineCurpos],
                            linePosition - lineCurpos);
                    cmd[lineCurpos] = ch;
                    
                    NX_Printf("%s", &cmd[lineCurpos]);

                    /* move the cursor to new position */
                    for (i = lineCurpos; i < linePosition; i++)
                        NX_Printf("\b");
                }
                else
                {
                    cmd[linePosition] = ch;
                    
                    NX_Printf("%c", ch);
                }

                linePosition ++;
                lineCurpos++;
                if (linePosition >= PWBOX_CMD_BUF_LEN)
                {
                    /* clear command line */
                    linePosition = 0;
                    lineCurpos = 0;
                }
            }
            break;
        }
    }
}

NX_PRIVATE NX_Error ExecuteCmd(char *cmdline)
{
    NX_Error err = NX_EOK;
    char appPath[NX_FILE_MAX_NAME] = {0};
    char *spacePos;
    char *cmd = cmdline;
    NX_U32 retCode = 0;

    while (*cmd == ' ') /* skip space in the head */
    {
        cmd++;
    }
    
    /* get app name */
    spacePos = NX_StrChr(cmd, ' ');
    if (spacePos == NX_NULL) /* no space, only app name */
    {
        NX_StrCopy(appPath, cmd);
    }
    else
    {
        NX_StrCopyN(appPath, cmd, spacePos - cmd + 1);
    }
#ifdef PWBOX_DEBUG
    NX_Printf("PowerBox: app name: `%s`\n", appPath);
#endif

    /* run build in cmd first */
    if (PowerBoxRunBuiltinCmd(appPath, cmd, powerBoxEnvLine) == NX_ENOSRCH)
    {
        NX_ProcessLaunch(appPath, NX_THREAD_CREATE_WAIT, &retCode, cmd, powerBoxEnvLine);
        err = NX_ErrorGet();
        if (err != NX_EOK)
        {
            NX_Printf("PowerBox: launch app `%s` failed with err %d/%s\n", appPath, err, NX_ErrorToString(err));
            return err;
        }
    }

#ifdef PWBOX_DEBUG
    NX_Printf("PowerBox: app `%s` return code %d\n", appPath, retCode);
#endif
    return err;
}

#define PWBOX_SCRIPT_BUF_SIZE 1024

NX_Error ExecuteScript(void)
{
    NX_Error err;
    int fd = NX_FileOpen(PWBOX_SCRIPT_PATH, NX_FILE_RDONLY, 0);
    if (fd < 0)
    {
        NX_Printf("no PowerBox script\n");
        return NX_EOK;
    }

    char fbuf[PWBOX_SCRIPT_BUF_SIZE];
    if (NX_FileRead(fd, fbuf, PWBOX_SCRIPT_BUF_SIZE) <= 0)
    {
        NX_Printf("read script failed!\n");
        return NX_ERROR;
    }

    NX_FileClose(fd);

    NX_Printf("PowerBox script:\n============\n%s\n============\n", fbuf);

    char cmdline[128];

    char *p = fbuf;
    while (*p)
    {
        NX_MemZero(cmdline, 128);
        char *q = cmdline;
        while (*p && *p != '\n' && *p != '\r')
        {
            *q++ = *p++;
        }
        /* skip new line */
        while (*p == '\n' || *p == '\r')
        {
            p++;
        }

        if (cmdline[0] && cmdline[0] != '#') /* execute cmd */
        {
            NX_Printf("--> PowerBox: do: %s\n", cmdline);
            if ((err = ExecuteCmd(cmdline)) != NX_EOK)
            {
                return err;
            }
        }
    }
    NX_Printf("execute PowerBox script done\n");
    return NX_EOK;
}

NX_Error NX_Main(char *cmdline, char *envline)
{
    NX_Printf("PowerBox go!\n");
    NX_Error err = NX_EOK;

    powerBoxEnvLine = envline;

    /* execute PowerBox script */
    ExecuteScript();
    
    while (1)
    {
        PrintPrompt(NX_NULL);
        lineCurpos = 0;
        linePosition = 0;
        // read cmd
        NX_MemZero(powerBoxCmdBuf, PWBOX_CMD_BUF_LEN);
        err = ReadCmd(0, powerBoxCmdBuf, PWBOX_CMD_BUF_LEN);
        if (err != NX_EOK)
        {
            if (err == NX_EAGAIN)
            {
                continue;
            }
            else
            {
                break;
            }
        }
        // exec cmd
        err = ExecuteCmd(powerBoxCmdBuf);
#ifdef PWBOX_0DEBUG
        NX_Printf("PowerBox: execute cmd: %s with state:%d/%s\n", powerBoxCmdBuf, err, NX_ErrorToString(err));
#endif
    }
    NX_Printf("PowerBox: abort with err %d/%s\n", err, NX_ErrorToString(err));
    return NX_EOK;
}
